/*  SPDX-License-Identifier: GPL-2.0-or-later */
/*!********************************************************************

  Audacity: A Digital Audio Editor

  GetAcidizerTags.cpp

  Matthieu Hodgkinson

**********************************************************************/
#include "libraries/lib-import-export/GetAcidizerTags.h"
#include "libraries/lib-file-formats/AcidizerTags.h"

#include <algorithm>
#include <array>
#include <cstring> // memset
#include <memory>

namespace LibImportExport {
std::optional<LibFileFormats::AcidizerTags> GetAcidizerTags(
    SNDFILE& file, const std::vector<std::string>& trustedDistributors)
{
    SF_LOOP_INFO loopInfo;
    if (
        sf_command(&file, SFC_GET_LOOP_INFO, &loopInfo, sizeof(loopInfo))
        == SF_FALSE) {
        return {};
    }

    if (
        loopInfo.loop_mode == SF_LOOP_BACKWARD
        || loopInfo.loop_mode == SF_LOOP_ALTERNATING) {
        // Don't know what that is:
        return {};
    }

    if (loopInfo.loop_mode == SF_LOOP_NONE) {
        return LibFileFormats::AcidizerTags::OneShot {};
    }

    if (loopInfo.num_beats != 0) {
        // Forward loop with number of beats set: all files like these I have seen
        // so far were correctly tagged.
        SF_INFO info;
        std::memset(&info, 0, sizeof(info));
        sf_command(&file, SFC_GET_CURRENT_SF_INFO, &info, sizeof(info));
        if (info.samplerate == 0 || info.frames == 0) {
            return {};
        }
        const auto duration = 1. * info.frames / info.samplerate;
        return LibFileFormats::AcidizerTags::Loop { 60. * loopInfo.num_beats
                                                    / duration };
    }

    // There is loop info, but in some unexpected combination. We don't trust it
    // unless it is from a trusted distributor.
    SF_CHUNK_INFO info;
    constexpr std::array<char, 4> listId = { 'L', 'I', 'S', 'T' };
    std::copy(listId.begin(), listId.end(), info.id);
    std::fill(info.id + sizeof(listId), info.id + sizeof(info.id), '\0');
    info.id_size = sizeof(listId);
    auto chunkIt = sf_get_chunk_iterator(&file, &info);
    while (chunkIt)
    {
        if (sf_get_chunk_size(chunkIt, &info) != SF_ERR_NO_ERROR) {
            break;
        }
        constexpr std::array<char, 4> INFO = { 'I', 'N', 'F', 'O' };
        constexpr std::array<char, 4> IDST = { 'I', 'D', 'S', 'T' };
        // Another 4 bytes after INFO and IDST that indicates the size of the data
        constexpr auto dataPos = sizeof(INFO) + sizeof(IDST) + 4;
        if (info.datalen < dataPos) {
            // Not the expected data
            continue;
        }
        const auto chars = std::make_unique<char[]>(info.datalen);
        info.data = chars.get();
        if (sf_get_chunk_data(chunkIt, &info) != SF_ERR_NO_ERROR) {
            break;
        }
        chunkIt = sf_next_chunk_iterator(chunkIt);

        auto pos = 0;
        const auto firstFour
            =std::string { chars.get() + pos, chars.get() + pos + sizeof(INFO) };
        if (firstFour != std::string { INFO.data(), INFO.size() }) {
            continue;
        }

        pos += sizeof(INFO);
        const auto nextFour
            =std::string { chars.get() + pos, chars.get() + pos + sizeof(IDST) };
        if (nextFour != std::string { IDST.data(), IDST.size() }) {
            continue;
        }

        // Ignore trailing nulls, which could be the result of byte-padding for
        // word alignment:
        const auto charsEnd = std::find_if(
            chars.get() + dataPos, chars.get() + info.datalen,
            [](const char c) { return c == '\0'; });
        const auto distributor = std::string { chars.get() + 12, charsEnd };
        const auto isTrusted
            =std::find(
                  trustedDistributors.begin(), trustedDistributors.end(),
                  distributor) != trustedDistributors.end();
        if (isTrusted) {
            // Later we may want to get the key, too, but for now we're only
            // interested in BPM.
            return LibFileFormats::AcidizerTags::Loop { loopInfo.bpm };
        }
    }

    // No luck:
    return {};
}
} // namespace LibImportExport
